package fms.util;
import java.io.*;
import java.net.SocketException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.log4j.Logger;
import org.apache.poi.util.IOUtils;

/**
 * Created with IntelliJ IDEA.
 * Desc: 1、FTP文件读取
 *       2、FTP目标目录文件名列表的获取
 *       3、FTP文件上传、下载
 *       4、FTP文件剪切、删除
 */

public class FtpUtils {

    private FTPClient ftpClient;
    private String strencoding = "UTF-8";       // "GBK";
    private String ip = "124.192.24.250";       // 服务器IP地址
    private String userName = "liliang";        // 用户名
    private String userPwd = "liliang";         // 密码
    private int port = 21;                      // 端口号
    private String path = "/";                  // 工作目录（可能是读文件的源目录，也可能是写文件的目标目录）

    private Logger ftpAccessLogger = Logger.getLogger("ftpAccess");

    /**
     * init ftp servere
     */
    public FtpUtils(String ip, String userName, String userPwd, int port, String path) {
        this.ip = ip;
        this.userName = userName;
        this.userPwd = userPwd;
        this.port = port;
        this.path = path;
    }

    public void connectServer() throws IOException {
        this.connect(ip, port, userName, userPwd, path);
    }

    /**
     * @param ip
     * @param port
     * @param userName
     * @param userPwd
     * @param path
     * @throws SocketException
     * function : 连接到服务器
     */
    private void connect(String ip, int port, String userName, String userPwd, String path) throws IOException {
        ftpClient = new FTPClient();

//        try {
// 一设置文件类型就异常了
//            ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
//            ftpClient.setControlEncoding("GBK");
//            ftpClient.setBufferSize(300000);
//            ftpClient.setDataTimeout(5000);
//            ftpClient.setConnectTimeout(5000);
//            ftpClient.setConnectTimeout(5000);
            ftpClient.enterLocalPassiveMode();
            // 连接
            ftpClient.connect(ip, port);
            // 登录
            ftpClient.login(userName, userPwd);
            if (path != null && path.length() > 0) {
                // 跳转到指定目录
                ftpClient.changeWorkingDirectory(path);
            }
            ftpAccessLogger.info("FTP服务器建立连接、用户登录成功！");
//        } catch (Exception e) {
//            ftpAccessLogger.error("FTP服务器建立连接、用户登录异常：" + e.toString());
//        }
    }

    /**
     * function : 关闭连接
     */
    public void closeServer() {
        if (ftpClient.isConnected()) {
            try {
                ftpClient.logout();
                ftpClient.disconnect();
                ftpAccessLogger.info("FTP服务器用户注销、断开连接成功！");
            } catch (Exception e) {
                ftpAccessLogger.error("FTP服务器用户注销、断开连接异常：" + e.toString());
            }
        }
    }

    /**
     * function : FTP上传单个文件，上传成功后默认删除本地文件（为下次不上传重复数据）
     * localDir : 本地待上传文件所在目录
     * fileName : 待上传文件名，不含路径
     * localBackupDir : 对于上传成功的文件备份目录，如果为空，就删除，不备份
     */
    public void uploadFile(String localDir, String localFileName, String localBackupDir) {
        File srcFile = null;
        FileInputStream fis = null;
        try {
            ftpAccessLogger.info("开始上传文件：" + localDir + java.io.File.separator + localFileName);
            srcFile = new File(localDir + java.io.File.separator + localFileName);
            fis = new FileInputStream(srcFile);
//            //设置上传目录
//            ftpClient.changeWorkingDirectory("/test");
//            ftpClient.setBufferSize(1024);
            // 添加下面两行代码才能保证上传到FTP的OFFICE文件是有效的（只传文本文件则无所谓）
            ftpClient.setControlEncoding("GBK");
//            //设置文件类型（二进制）
            ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
            boolean bRes = ftpClient.storeFile(localFileName, fis);
            if (bRes) {
                ftpAccessLogger.info("上传文件[成功]：" + localDir + java.io.File.separator + localFileName);
            } else {
                ftpAccessLogger.error("上传文件[失败]：" + localDir + java.io.File.separator + localFileName);
            }
        } catch (Exception e) {
            ftpAccessLogger.info("上传文件异常：" + e.toString());
        } finally {
            IOUtils.closeQuietly(fis);
            // 未指定备份目录，则删除
            if (ObjectUtil.isBlank(localBackupDir)) {
                Boolean bRes = srcFile.delete();
                if (bRes) {
                    ftpAccessLogger.info("删除上传文件[成功]：" + localDir + java.io.File.separator + localFileName);
                } else {
                    ftpAccessLogger.error("删除上传文件[失败]：" + localDir + java.io.File.separator + localFileName);
                }
            } else {
//System.out.println(localDir + java.io.File.separator + localFileName);
//System.out.println(localBackupDir + java.io.File.separator + localFileName);
                Boolean bRes = FileUtil.MoveFile(localDir + java.io.File.separator + localFileName, localBackupDir);
                if (bRes) {
                    ftpAccessLogger.info("备份上传文件[成功]：" + localDir + java.io.File.separator + localFileName);
                } else {
                    ftpAccessLogger.error("备份上传文件[失败]：" + localDir + java.io.File.separator + localFileName);
                }
            }

//            try {
//                ftpClient.disconnect();
//            } catch (IOException e) {
//                e.printStackTrace();
//                throw new RuntimeException("关闭FTP连接发生异常", e);
//            }
        }
    }

    /**
     * function : FTP上传批量文件测试，上传成功后默认删除本地文件（为下次不上传重复数据）
     * localDir : 本地待上传文件集所在目录
     * fileName : 待上传文件名列表，不含路径
     * localBackupDir : 对于上传成功的文件备份目录，如果为空，就删除，不备份
     */
    public void uploadFile(String localDir, List localFileName, String localBackupDir) {
        for (int i = 0; i < localFileName.size(); i++) {
            File srcFile = null;
            FileInputStream fis = null;
            try {
                srcFile = new File(localDir + java.io.File.separator + localFileName.get(i));
                fis = new FileInputStream(srcFile);
                ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
                ftpClient.setControlEncoding("GBK");
                boolean bRes = ftpClient.storeFile((String)localFileName.get(i), fis);
                if (bRes) {
                    ftpAccessLogger.info("上传文件[成功]：" + localDir + java.io.File.separator + localFileName.get(i));
                } else {
                    ftpAccessLogger.info("上传文件[失败]：" + localDir + java.io.File.separator + localFileName.get(i));
                }
            } catch (Exception e) {
                ftpAccessLogger.info("上传文件异常：" + e.toString());
            } finally {
                IOUtils.closeQuietly(fis);
                // 未指定备份目录，则删除
                if (ObjectUtil.isBlank(localBackupDir)) {
                    Boolean bRes = srcFile.delete();
                    if (bRes) {
                        ftpAccessLogger.info("删除上传文件[成功]：" + localDir + java.io.File.separator + localFileName.get(i));
                    } else {
                        ftpAccessLogger.error("删除上传文件[失败]：" + localDir + java.io.File.separator + localFileName.get(i));
                    }
                } else {
                    Boolean bRes = FileUtil.MoveFile(localDir + java.io.File.separator + localFileName.get(i), localBackupDir);
                    if (bRes) {
                        ftpAccessLogger.info("备份上传文件[成功]：" + localDir + java.io.File.separator + localFileName.get(i));
                    } else {
                        ftpAccessLogger.error("备份上传文件[失败]：" + localDir + java.io.File.separator + localFileName.get(i));
                    }
                }
            }
        }
    }

    /**
     * function : FTP下载单个文件测试，下载成功后默认删除ftp文件（为下次不下载重复数据）
     * localDir : 本地存放下载文件的目录
     * fileName : ftp文件下载到本地后的文件名
     * ftpBackupDir : 对于下载成功的文件备份目录，如果为空，就删除，不备份
     */
    public void downloadFile(String localDir, String ftpFileName, String ftpBackupDir) {
        FileOutputStream fos = null;
        try {
            String remoteFileName = path + '/' + ftpFileName;
            fos = new FileOutputStream(localDir + java.io.File.separator + ftpFileName);

            // 添加下面两行代码才能保证上传到FTP的OFFICE文件是有效的（只传文本文件则无所谓）
            ftpClient.setControlEncoding("GBK");
//            //设置文件类型（二进制）
            ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
            boolean bRes = ftpClient.retrieveFile(remoteFileName, fos);
            if (bRes) {
                ftpAccessLogger.info("下载文件[成功]：" + path + '/' + ftpFileName);
            } else {
                ftpAccessLogger.info("下载文件[失败]：" + path + '/' + ftpFileName);
            }
        } catch (IOException e) {
            ftpAccessLogger.info("下载文件异常：" + e.toString());
        } finally {
            IOUtils.closeQuietly(fos);
            try {
                // 未指定备份目录，则删除
                if (ObjectUtil.isBlank(ftpBackupDir)) {
                    Boolean bRes = ftpClient.deleteFile(path + '/' + ftpFileName);
                    if (bRes) {
                        ftpAccessLogger.info("删除下载文件[成功]：" + path + '/' + ftpFileName);
                    } else {
                        ftpAccessLogger.error("删除下载文件[失败]：" + path + '/' + ftpFileName);
                    }
                } else {
                    Boolean bRes = ftpClient.rename(path + '/' + ftpFileName, ftpBackupDir + '/' + ftpFileName);
                    if (bRes) {
                        ftpAccessLogger.info("备份下载文件[成功]：" + path + '/' + ftpFileName);
//                        ftpClient.deleteFile(path + '/' + ftpFileName);
                    } else {
                        ftpAccessLogger.error("备份下载文件[失败]：" + path + '/' + ftpFileName);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
//            try {
//                ftpClient.disconnect();
//            } catch (IOException e) {
//                e.printStackTrace();
//                throw new RuntimeException("关闭FTP连接发生异常", e);
//            }
        }
    }

    /**
     * function : FTP批量下载文件测试
     * localDir : 本地存放下载文件的目录
     * fileName : ftp文件名列表 = 下载到本地后的文件名列表
     */
    public void downloadFile(String localDir, List ftpFileName, String ftpBackupDir) {
        for (int i = 0; i < ftpFileName.size(); i++) {
            FileOutputStream fos = null;
            try {
                String remoteFileName = path + '/' + ftpFileName.get(i);
                fos = new FileOutputStream(localDir + java.io.File.separator + ftpFileName.get(i));
                // 添加下面两行代码才能保证上传到FTP的OFFICE文件是有效的（只传文本文件则无所谓）
                ftpClient.setControlEncoding("GBK");
//            //设置文件类型（二进制）
                ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
                boolean bRes = ftpClient.retrieveFile(remoteFileName, fos);
                if (bRes) {
                    ftpAccessLogger.info("下载文件[成功]：" + path + '/' + ftpFileName.get(i));
                } else {
                    ftpAccessLogger.info("下载文件[失败]：" + path + '/' + ftpFileName.get(i));
                }
            } catch (IOException e) {
                ftpAccessLogger.info("下载文件异常：" + e.toString());
            } finally {
                IOUtils.closeQuietly(fos);
                try {
                    // 未指定备份目录，则删除
                    if (ObjectUtil.isBlank(ftpBackupDir)) {
                        Boolean bRes = ftpClient.deleteFile(path + '/' + ftpFileName.get(i));
                        if (bRes) {
                            ftpAccessLogger.info("删除下载文件[成功]：" + path + '/' + ftpFileName.get(i));
                        } else {
                            ftpAccessLogger.error("删除下载文件[失败]：" + path + '/' + ftpFileName.get(i));
                        }
                    } else {
                        Boolean bRes = ftpClient.rename(path + '/' + ftpFileName.get(i), ftpBackupDir + '/' + ftpFileName.get(i));
                        if (bRes) {
                            ftpAccessLogger.info("备份下载文件[成功]：" + path + '/' + ftpFileName.get(i));
//                            ftpClient.deleteFile(path + '/' + ftpFileName);
                        } else {
                            ftpAccessLogger.error("备份下载文件[失败]：" + path + '/' + ftpFileName.get(i));
                        }
                    }
                } catch (Exception e) {
                    ftpAccessLogger.info("下载文件删除备份异常：" + e.toString());
                }
            }
        }
    }

    /**
     * @param path : 从“/”开始
     * @return function : 读取指定目录下的所有文件(不含目录)
     * @throws IOException
     */
    public List<String> getFileList(String path) {
        List<String> fileLists = new ArrayList<String>();
        // 获得指定目录下所有文件名
        FTPFile[] ftpFiles = null;
        try {
            ftpFiles = ftpClient.listFiles(path);
        } catch (IOException e) {
            e.printStackTrace();
        }
        for (int i = 0; ftpFiles != null && i < ftpFiles.length; i++) {
            FTPFile file = ftpFiles[i];
            if (file.isFile()) {
                fileLists.add(file.getName());
            }
        }
        return fileLists;
    }


    /**
     * @param
     * @return function : 获取远程服务器上指定文件的InputStream对象
     * @throws Exception
     */
//    public InputStream  getFtpFileInputStream(String fileName) throws Exception {
//        System.out.println(fileName);
//        InputStream isRes = ftpClient.retrieveFileStream(fileName);
//        System.out.println("ftpClient.retrieveFileStream(fileName)返回值为:" + isRes);
//        // 主动调用一次getReply()把接下来的226消费掉. 这样做是可以解决这个返回null问题 ==> 如果不写，在调用retrieveFileStream后，文件无法重命名
//        ftpClient.getReply();
//        return isRes;
//    }
    /**
     * @param
     * @return function : 获取远程服务器上指定文件的内容;由于上面的方法在读取大文件(190k以上)时，会卡死在getReply语句上;
     *                    且在执行完retrieveFileStream语句后，又必须执行getReply（断开连接就能实现这个效果）;
     *                    所以，想实现读取完文件后，删除or备份操作，就的由两个ftpClient实现
     * @throws Exception
     */
    public String getFtpFileInputStream(String fileName) throws Exception {
        InputStream isRes = ftpClient.retrieveFileStream(fileName);
//        System.out.println("ftpClient.retrieveFileStream(fileName)返回值为:" + isRes);

        BufferedReader reader = new BufferedReader(new InputStreamReader(isRes, strencoding));
        String line;
        StringBuilder builder = new StringBuilder(150);
        while ((line = reader.readLine()) != null) {
//                System.out.println(line);
            builder.append(line + System.getProperty("line.separator"));
        }
        if (null != isRes)
            isRes.close();
        if (null != reader)
            reader.close();

//        System.out.println("xml实际内容为:" + builder);

        // 主动调用一次getReply()把接下来的226消费掉. 这样做是可以解决这个返回null问题 ==> 如果不写，在调用retrieveFileStream后，文件无法重命名
        // ftpClient.getReply();
//        closeServer();
        return builder.toString();
    }

    /**
     * @param
     * @return function : 读取文件（三种方式均可：/test/1.txt; test/1.txt; 1.txt）
     * @throws ParseException
     * @throws IOException
     */
    public String readFile(String fileName) throws ParseException {
        InputStream ins = null;
        StringBuilder builder = null;
        try {
            // 从服务器上读取指定的文件
            ins = ftpClient.retrieveFileStream(fileName);
            BufferedReader reader = new BufferedReader(new InputStreamReader(ins, strencoding));
            String line;
            builder = new StringBuilder(150);
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
                builder.append(line);
            }
            reader.close();
            if (ins != null) {
                ins.close();
            }
            // 主动调用一次getReply()把接下来的226消费掉. 这样做是可以解决这个返回null问题
            ftpClient.getReply();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return builder.toString();
    }

    /**
     * @deprecated ：删除FTP上的指定文件
     * @param fileName ：文件名
     */
    public boolean deleteFile(String fileName) {
        boolean bRes = false;
        try {
            bRes = ftpClient.deleteFile(fileName);
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("deleteFile bRes: " + bRes);
        return bRes;
    }

    /**
     * @param fileNameFrom ：原文件名称
     * @param fileNameTo ：目标文件名称
     * @DESC ：FTP源文件移动后，需要刷新看效果
     */
    public boolean renameFile(String fileNameFrom, String fileNameTo) {
        boolean bRes = false;
        try {
            bRes = ftpClient.rename(fileNameFrom, fileNameTo);;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bRes;
    }

}